<script setup>
import { ref, onMounted, onBeforeUnmount, watch, nextTick } from 'vue';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
import { ElMessage, ElTooltip, ElButton } from 'element-plus';

const props = defineProps({
  selectedMeridian: Object,
  selectedAcupoint: Object,
});

// References
const container = ref(null);
const loadingProgress = ref(0);
const isLoading = ref(true);
const loadingError = ref(false);
const errorMessage = ref('');
const viewMode = ref('normal'); // normal, wireframe, x-ray

// Three.js variables
let scene, camera, renderer, controls, model, mixer;
let clock = new THREE.Clock();
let meridianHighlights = {};
let acupointMarkers = {};
let animationFrameId = null;

// Materials for different purposes
const highlightMeridianMaterial = new THREE.MeshStandardMaterial({
  color: 0xff3333,
  emissive: 0xff0000,
  emissiveIntensity: 0.7,
  transparent: true,
  opacity: 0.8,
  roughness: 0.3,
  metalness: 0.8
});

const highlightAcupointMaterial = new THREE.MeshBasicMaterial({
  color: 0xffaa00,
  emissive: 0xffaa00,
  emissiveIntensity: 1.0,
  transparent: true,
  opacity: 0.9
});

const xRayMaterial = new THREE.MeshBasicMaterial({
  color: 0x00aaff,
  transparent: true,
  opacity: 0.3,
  wireframe: false,
  depthWrite: false,
  side: THREE.DoubleSide
});

const wireframeMaterial = new THREE.MeshBasicMaterial({
  color: 0x00ff00,
  wireframe: true
});

// Reset camera to default position
const resetCamera = () => {
  if (!camera || !controls) return;
  
  camera.position.set(0, 1.5, 3);
  camera.lookAt(0, 1, 0);
  controls.target.set(0, 1, 0);
  controls.update();
};

// Setup the scene
const setupScene = () => {
  // Create scene
  scene = new THREE.Scene();
  scene.background = new THREE.Color(0xf8f8f8);

  // Create camera
  camera = new THREE.PerspectiveCamera(
    45,
    container.value.clientWidth / container.value.clientHeight,
    0.1,
    1000
  );
  camera.position.set(0, 1.5, 3);

  // Create renderer
  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setSize(container.value.clientWidth, container.value.clientHeight);
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.outputColorSpace = THREE.SRGBColorSpace;
  renderer.shadowMap.enabled = true;
  renderer.shadowMap.type = THREE.PCFSoftShadowMap;
  container.value.appendChild(renderer.domElement);

  // Add lights
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.6);
  scene.add(ambientLight);

  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
  directionalLight.position.set(5, 10, 5);
  directionalLight.castShadow = true;
  
  // Improve shadow quality
  directionalLight.shadow.mapSize.width = 1024;
  directionalLight.shadow.mapSize.height = 1024;
  directionalLight.shadow.camera.near = 0.5;
  directionalLight.shadow.camera.far = 50;
  scene.add(directionalLight);
  
  // Add fill light from opposite side
  const fillLight = new THREE.DirectionalLight(0xffffff, 0.3);
  fillLight.position.set(-5, 5, -5);
  scene.add(fillLight);

  // Add a small light from below for better visibility
  const rimLight = new THREE.DirectionalLight(0xffffff, 0.2);
  rimLight.position.set(0, -5, 0);
  scene.add(rimLight);

  // Create controls
  controls = new OrbitControls(camera, renderer.domElement);
  controls.enableDamping = true;
  controls.dampingFactor = 0.1;
  controls.minDistance = 1.0;
  controls.maxDistance = 6;
  controls.maxPolarAngle = Math.PI - 0.1; // Prevent going below the model
  controls.minPolarAngle = 0.1; // Prevent going too high above the model
  controls.enablePan = true;
  controls.screenSpacePanning = true;
  controls.rotateSpeed = 0.7;
  controls.zoomSpeed = 1.2;
  controls.panSpeed = 0.8;
  controls.target.set(0, 1, 0);
};

// Load the 3D model
const loadModel = () => {
  const loader = new GLTFLoader();
  
  loader.load(
    '/models/human_meridian_system-processed.glb',
    (gltf) => {
      model = gltf.scene;
      
      // Adjust scale and position if needed
      model.scale.set(1, 1, 1);
      model.position.set(0, -0.2, 0); // Slightly lower to center in view
      
      // Make sure model casts and receives shadows
      model.traverse((node) => {
        if (node.isMesh) {
          node.castShadow = true;
          node.receiveShadow = true;
          
          // Store original material for later restoration
          node.userData.originalMaterial = node.material.clone();
          
          // Enable material properties for lighting
          node.material.roughness = 0.6;
          node.material.metalness = 0.1;
        }
      });
      
      scene.add(model);

      // Set initial rotation to show front view
      model.rotation.y = Math.PI; // Rotate to face camera
      
      // Setup animations if they exist
      if (gltf.animations && gltf.animations.length) {
        mixer = new THREE.AnimationMixer(model);
        const action = mixer.clipAction(gltf.animations[0]);
        action.play();
      }
      
      // Create a mapping of meridians and acupoints
      setupMeridianMaps();
      
      // Try to highlight anything that's already selected
      nextTick(() => {
        if (props.selectedMeridian) {
          highlightMeridian(props.selectedMeridian.no);
        }
        if (props.selectedAcupoint) {
          highlightAcupoint(props.selectedAcupoint.no);
        }
      });
      
      isLoading.value = false;
    },
    (xhr) => {
      loadingProgress.value = Math.floor((xhr.loaded / xhr.total) * 100);
    },
    (error) => {
      console.error('Error loading model:', error);
      isLoading.value = false;
      loadingError.value = true;
      errorMessage.value = '无法加载3D模型，请刷新重试: ' + error.message;
      ElMessage.error('3D模型加载失败，请刷新重试');
    }
  );
};

// Setup meridian maps for highlighting
const setupMeridianMaps = () => {
  if (!model) return;
  
  // In this implementation, we would create a proper mapping
  // of meridians and acupoints based on the model structure.
  // For now, we'll use a simplistic approach based on mesh names
  
  let meridianCount = 0;
  
  model.traverse((child) => {
    if (child.isMesh) {
      // Store original materials to restore later
      child.userData.originalMaterial = child.material.clone();
      
      // Try to parse the meridian information from mesh names or userData
      // This is a placeholder logic - the actual implementation would depend
      // on how your 3D model is structured
      if (child.name.includes('meridian') || child.name.includes('Meridian')) {
        meridianCount++;
        
        // Extract meridian code if available in name
        const meridianMatch = child.name.match(/[A-Z]{2}\d*/);
        if (meridianMatch) {
          const meridianCode = meridianMatch[0];
          if (!child.userData.meridianInfo) {
            child.userData.meridianInfo = { code: meridianCode };
          }
        }
      }
      
      // Similarly for acupoints
      if (child.name.includes('acupoint') || child.name.includes('Acupoint')) {
        // Extract acupoint code if available
        const acupointMatch = child.name.match(/[A-Z]{2}\d+/);
        if (acupointMatch) {
          const acupointCode = acupointMatch[0];
          if (!child.userData.acupointInfo) {
            child.userData.acupointInfo = { code: acupointCode };
          }
        }
      }
    }
  });
  
  console.log(`Processed model with approximately ${meridianCount} meridian parts`);
};

// Change view mode (normal, wireframe, x-ray)
const setViewMode = (mode) => {
  if (!model) return;
  
  viewMode.value = mode;
  
  // Reset all materials first
  model.traverse((child) => {
    if (child.isMesh && child.userData.originalMaterial) {
      child.material = child.userData.originalMaterial.clone();
    }
  });
  
  // Clear any highlights
  clearHighlights();
  clearAcupointMarkers();
  
  // Apply the selected view mode
  model.traverse((child) => {
    if (child.isMesh) {
      switch (mode) {
        case 'wireframe':
          child.material = wireframeMaterial.clone();
          break;
        case 'x-ray':
          child.material = xRayMaterial.clone();
          break;
        case 'normal':
        default:
          // Already reset to original above
          break;
      }
    }
  });
  
  // Re-apply highlights if needed
  if (props.selectedMeridian) {
    highlightMeridian(props.selectedMeridian.no);
  }
  if (props.selectedAcupoint) {
    highlightAcupoint(props.selectedAcupoint.no);
  }
};

// Highlight a meridian
const highlightMeridian = (meridianNo) => {
  // Clear any existing highlights
  clearHighlights();
  
  // If no model or no meridian number, exit
  if (!model || !meridianNo) return;
  
  let highlightCount = 0;
  
  // First try to find exact matches by meridian code
  model.traverse((child) => {
    if (child.isMesh) {
      // Check if the mesh name contains the meridian code
      if (child.name.includes(meridianNo) ||
          (child.userData.meridianInfo && child.userData.meridianInfo.code === meridianNo)) {
        
        // Store original material if not already stored
        if (!child.userData.originalMaterial) {
          child.userData.originalMaterial = child.material.clone();
        }
        
        // Apply highlight material (clone to avoid cross-references)
        child.material = highlightMeridianMaterial.clone();
        meridianHighlights[child.uuid] = child;
        highlightCount++;
      }
    }
  });
  
  // If no exact matches were found, try a broader search
  if (highlightCount === 0) {
    // Fallback: Try to highlight based on meridian name parts
    // For example, BL might be represented as "bladder" in mesh names
    const meridianNameMappings = {
      'BL': ['bladder', 'pangguang', 'foot-taiyang'],
      'GB': ['gallbladder', 'dan'],
      'LI': ['large-intestine', 'dachang'],
      'ST': ['stomach', 'wei'],
      'SP': ['spleen', 'pi'],
      'HT': ['heart', 'xin'],
      'SI': ['small-intestine', 'xiaochang'],
      'LR': ['liver', 'gan'],
      'LU': ['lung', 'fei'],
      'KI': ['kidney', 'shen'],
      'PC': ['pericardium', 'xinbao'],
      'TE': ['triple-energizer', 'sanjiao']
    };
    
    // Get possible name parts for this meridian code
    const nameParts = meridianNameMappings[meridianNo.replace(/\d+/g, '')] || [];
    
    if (nameParts.length > 0) {
      model.traverse((child) => {
        if (child.isMesh) {
          // Check if any name parts match
          const lowerName = child.name.toLowerCase();
          if (nameParts.some(part => lowerName.includes(part))) {
            // Store original material
            if (!child.userData.originalMaterial) {
              child.userData.originalMaterial = child.material.clone();
            }
            
            // Apply highlight material
            child.material = highlightMeridianMaterial.clone();
            meridianHighlights[child.uuid] = child;
            highlightCount++;
          }
        }
      });
    }
  }
  
  // If still no matches, create a highlighted path along the meridian
  if (highlightCount === 0) {
    console.log(`Could not find specific meshes for meridian ${meridianNo}. Creating fallback visualization.`);
    
    // This would be a more complex implementation in a real app,
    // using the acupoints along the meridian to create a path
    
    // For demo purposes, we'll create a simple visual marker
    createMeridianPathMarker(meridianNo);
  }
};

// Create a visual marker for a meridian when specific mesh not found
const createMeridianPathMarker = (meridianNo) => {
  // This is a placeholder function that would be implemented
  // to create a visual representation of a meridian path
  // based on the positions of acupoints along that meridian
  
  // In a real app, you would:
  // 1. Get all acupoints for this meridian
  // 2. Extract their 3D positions
  // 3. Create a curved line connecting them
  // 4. Add the line to the scene
  
  // For now we'll just show a notification
  ElMessage({
    message: `正在显示经络: ${meridianNo}`,
    type: 'info',
    duration: 2000
  });
};

// Highlight an acupoint
const highlightAcupoint = (acupointNo) => {
  // Clear any existing acupoint markers
  clearAcupointMarkers();
  
  if (!model || !acupointNo) return;
  
  let acupointFound = false;
  
  // First try to find the exact acupoint mesh in the model
  model.traverse((child) => {
    if (child.isMesh) {
      // Check if the mesh represents this acupoint
      if (child.name.includes(acupointNo) ||
          (child.userData.acupointInfo && child.userData.acupointInfo.code === acupointNo)) {
        
        // Store original material
        if (!child.userData.originalMaterial) {
          child.userData.originalMaterial = child.material.clone();
        }
        
        // Apply highlight material
        child.material = highlightAcupointMaterial.clone();
        acupointMarkers[child.uuid] = child;
        
        // Store a reference to use for pulsing animation
        child.userData.isAcupointMarker = true;
        
        acupointFound = true;
      }
    }
  });
  
  // If no exact acupoint was found in the model, create a marker
  if (!acupointFound) {
    // In a real app, we would have a database of acupoint positions
    // For now, we'll create a marker at an approximate position
    
    // Create a sphere geometry for the marker
    const markerGeometry = new THREE.SphereGeometry(0.03, 32, 32);
    const markerMaterial = highlightAcupointMaterial.clone();
    
    const marker = new THREE.Mesh(markerGeometry, markerMaterial);
    
    // Position the marker - this is a placeholder
    // In a real app, this would use actual coordinates from a database
    
    // For demo, place marker based on meridian and acupoint patterns
    const meridianCode = acupointNo.replace(/\d+/g, '');
    const acupointNumber = parseInt(acupointNo.replace(/\D+/g, '')) || 1;
    
    // Simple mapping of meridians to body regions for demo purposes
    const positionMap = {
      'BL': { x: 0.15, y: acupointNumber / 20, z: -0.1 }, // Back
      'GB': { x: 0.25, y: acupointNumber / 30, z: 0 },    // Side
      'ST': { x: 0.15, y: acupointNumber / 30, z: 0.1 },  // Front
      'LI': { x: 0.3, y: 1.5 - acupointNumber / 30, z: 0.1 }, // Arm
      'LU': { x: 0.2, y: 1.4 - acupointNumber / 30, z: 0.1 }, // Chest/arm
      'SP': { x: 0.1, y: 0.7 - acupointNumber / 40, z: 0.1 },  // Leg
      'LR': { x: 0.08, y: 0.6 - acupointNumber / 40, z: 0.05 }, // Leg
      'KI': { x: 0.05, y: 0.6 - acupointNumber / 40, z: 0 },    // Back/leg
      'HT': { x: 0.15, y: 1.3 - acupointNumber / 30, z: 0.1 },  // Arm
      'PC': { x: 0.12, y: 1.3 - acupointNumber / 30, z: 0.08 }, // Arm
      'SI': { x: 0.25, y: 1.3 - acupointNumber / 30, z: -0.1 }, // Back/arm
      'TE': { x: 0.28, y: 1.3 - acupointNumber / 30, z: 0.05 }  // Arm
    };
    
    const position = positionMap[meridianCode] || { x: 0, y: 1, z: 0 };
    
    // Apply the calculated position
    marker.position.set(position.x, position.y, position.z);
    
    // For bilateral meridians, create a marker on both sides
    if (['BL', 'GB', 'ST', 'LI', 'SP', 'LR', 'KI', 'TE', 'SI', 'LU'].includes(meridianCode)) {
      const markerRight = marker.clone();
      markerRight.position.x = -position.x;
      scene.add(markerRight);
      acupointMarkers['right-' + acupointNo] = markerRight;
      
      // Add pulsing animation properties
      markerRight.userData.isAcupointMarker = true;
    }
    
    scene.add(marker);
    acupointMarkers['left-' + acupointNo] = marker;
    
    // Add pulsing animation properties
    marker.userData.isAcupointMarker = true;
    
    // Focus camera on this acupoint (optional)
    // controls.target.copy(marker.position);
    // controls.update();
  }
  
  // Show message about the highlighted acupoint
  ElMessage({
    message: `正在显示穴位: ${acupointNo}`,
    type: 'success',
    duration: 2000
  });
};

// Clear all highlights
const clearHighlights = () => {
  // Restore original materials
  Object.values(meridianHighlights).forEach(mesh => {
    if (mesh.userData.originalMaterial) {
      mesh.material = mesh.userData.originalMaterial.clone();
    }
  });
  
  meridianHighlights = {};
};

// Clear acupoint markers
const clearAcupointMarkers = () => {
  Object.values(acupointMarkers).forEach(marker => {
    // If it's a separate marker object (not part of the model)
    if (!marker.userData.originalMaterial) {
      scene.remove(marker);
    } else {
      // If it's part of the model, restore original material
      marker.material = marker.userData.originalMaterial.clone();
    }
  });
  
  acupointMarkers = {};
};

// Animation loop
const animate = () => {
  animationFrameId = requestAnimationFrame(animate);
  
  if (controls) controls.update();
  
  if (mixer) {
    mixer.update(clock.getDelta());
  }
  
  // Update pulsing animations for acupoint markers
  Object.values(acupointMarkers).forEach(marker => {
    if (marker.userData.isAcupointMarker) {
      const scale = 1 + 0.3 * Math.sin(Date.now() * 0.005);
      marker.scale.set(scale, scale, scale);
      
      // Also pulse opacity for better visibility
      if (marker.material) {
        const opacity = 0.6 + 0.4 * Math.sin(Date.now() * 0.005);
        marker.material.opacity = opacity;
      }
    }
  });
  
  renderer.render(scene, camera);
};

// Handle window resize
const handleResize = () => {
  if (!camera || !renderer || !container.value) return;
  
  camera.aspect = container.value.clientWidth / container.value.clientHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(container.value.clientWidth, container.value.clientHeight);
};

// Watch for changes in selected meridian and acupoint
watch(() => props.selectedMeridian, (newMeridian) => {
  if (newMeridian) {
    highlightMeridian(newMeridian.no);
  } else {
    clearHighlights();
  }
}, { deep: true });

watch(() => props.selectedAcupoint, (newAcupoint) => {
  if (newAcupoint) {
    highlightAcupoint(newAcupoint.no);
  } else {
    clearAcupointMarkers();
  }
}, { deep: true });

// Function to take a screenshot
const takeScreenshot = () => {
  if (!renderer) return;
  
  // Render a frame to ensure latest state
  renderer.render(scene, camera);
  
  // Get the data URL from the canvas
  try {
    const dataURL = renderer.domElement.toDataURL('image/png');
    
    // Create a temporary link element
    const link = document.createElement('a');
    link.href = dataURL;
    link.download = `meridian-view-${new Date().toISOString().slice(0,10)}.png`;
    
    // Trigger download
    document.body.appendChild(link);
    link.click();
    document.body.removeChild(link);
    
    ElMessage.success('截图已保存');
  } catch (e) {
    console.error('Screenshot failed:', e);
    ElMessage.error('截图失败');
  }
};

// Lifecycle hooks
onMounted(() => {
  if (container.value) {
    setupScene();
    loadModel();
    window.addEventListener('resize', handleResize);
    animate();
  }
});

onBeforeUnmount(() => {
  window.removeEventListener('resize', handleResize);
  
  if (animationFrameId) {
    cancelAnimationFrame(animationFrameId);
  }
  
  if (renderer) {
    renderer.dispose();
    if (container.value) {
      container.value.removeChild(renderer.domElement);
    }
  }
  
  // Clean up THREE.js resources
  if (scene) {
    scene.traverse((object) => {
      if (object.geometry) {
        object.geometry.dispose();
      }
      
      if (object.material) {
        if (Array.isArray(object.material)) {
          for (let i = 0; i < object.material.length; i++) {
            object.material[i].dispose();
          }
        } else {
          object.material.dispose();
        }
      }
    });
  }
});
</script>

<template>
  <div class="model-viewer">
    <!-- Loading overlay -->
    <div v-if="isLoading" class="loading-overlay">
      <div class="loading-content">
        <div class="progress-bar">
          <div class="progress" :style="{ width: `${loadingProgress}%` }"></div>
        </div>
        <div class="loading-text">加载3D模型... {{ loadingProgress }}%</div>
      </div>
    </div>
    
    <!-- Error overlay -->
    <div v-if="loadingError" class="error-overlay">
      <div class="error-content">
        <i class="el-icon-error"></i>
        <div class="error-message">{{ errorMessage }}</div>
        <ElButton type="primary" @click="loadModel">重新加载</ElButton>
      </div>
    </div>
    
    <!-- 3D Model container -->
    <div ref="container" class="model-container"></div>
    
    <!-- Controls bar -->
    <div class="model-controls">
      <div class="control-group">
        <ElTooltip content="重置视角" placement="top">
          <ElButton circle icon="Refresh" @click="resetCamera"></ElButton>
        </ElTooltip>
        
        <ElTooltip content="截图" placement="top">
          <ElButton circle icon="Camera" @click="takeScreenshot"></ElButton>
        </ElTooltip>
      </div>
      
      <div class="view-mode-controls">
        <ElTooltip content="正常视图" placement="top">
          <ElButton
            :type="viewMode === 'normal' ? 'primary' : 'default'"
            @click="setViewMode('normal')"
          >
            正常
          </ElButton>
        </ElTooltip>
        
        <ElTooltip content="线框视图" placement="top">
          <ElButton
            :type="viewMode === 'wireframe' ? 'primary' : 'default'"
            @click="setViewMode('wireframe')"
          >
            线框
          </ElButton>
        </ElTooltip>
        
        <ElTooltip content="透视视图" placement="top">
          <ElButton
            :type="viewMode === 'x-ray' ? 'primary' : 'default'"
            @click="setViewMode('x-ray')"
          >
            透视
          </ElButton>
        </ElTooltip>
      </div>
    </div>
    
    <!-- Controls hint -->
    <div class="controls-hint">
      <i class="el-icon-mouse"></i> 旋转视角 |
      <i class="el-icon-d-arrow-right"></i> 缩放 |
      <i class="el-icon-rank"></i> 平移
    </div>
  </div>
</template>

<style scoped>
.model-viewer {
  position: relative;
  width: 100%;
  height: 65%;
  background-color: #f8f8f8;
  overflow: hidden;
  border-radius: 8px;
  box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.05);
}

.model-container {
  width: 100%;
  height: 100%;
}

.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(4px);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.loading-content {
  width: 80%;
  max-width: 400px;
  text-align: center;
  padding: 30px;
  background-color: rgba(255, 255, 255, 0.1);
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}

.progress-bar {
  width: 100%;
  height: 8px;
  background-color: rgba(255, 255, 255, 0.2);
  border-radius: 4px;
  overflow: hidden;
  margin-bottom: 15px;
}

.progress {
  height: 100%;
  background-color: var(--primary-color);
  border-radius: 4px;
  transition: width 0.3s ease;
}

.loading-text {
  color: white;
  font-size: 16px;
  font-weight: 500;
}

.error-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.7);
  backdrop-filter: blur(4px);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.error-content {
  width: 80%;
  max-width: 400px;
  text-align: center;
  padding: 30px;
  background-color: rgba(255, 255, 255, 0.9);
  border-radius: 12px;
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.2);
}

.error-content i {
  font-size: 40px;
  color: #f56c6c;
  margin-bottom: 20px;
}

.error-message {
  color: #606266;
  margin-bottom: 20px;
}

.model-controls {
  position: absolute;
  bottom: 40px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 10px;
  z-index: 5;
}

.control-group {
  display: flex;
  gap: 10px;
}

.view-mode-controls {
  display: flex;
  gap: 5px;
  background-color: rgba(255, 255, 255, 0.8);
  padding: 5px;
  border-radius: 4px;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

.controls-hint {
  position: absolute;
  bottom: 10px;
  left: 0;
  width: 100%;
  text-align: center;
  color: #606266;
  font-size: 12px;
  padding: 5px;
  background-color: rgba(255, 255, 255, 0.8);
  border-radius: 4px;
  backdrop-filter: blur(2px);
}

/* Icon styles */
.el-icon-mouse:before {
  content: "🖱️";
}

.el-icon-d-arrow-right:before {
  content: "🖲️";
}

.el-icon-rank:before {
  content: "👆";
}

.el-icon-error:before {
  content: "❌";
}

@media (max-width: 768px) {
  .model-controls {
    bottom: 50px;
  }
  
  .view-mode-controls {
    flex-direction: row;
  }
}
</style>